import { getLogger } from '@gnlab/utils'
import pick from 'lodash/pick'
import { makeHTTPSAvatar } from '@/lib/https'
import { type VideonumWord } from '@/services/common/definition'
import { getFilenameFromPath } from '@/services/utils'

const logger = getLogger('services/chat/definition')

/**
 * ===========================
 * 基础聊天记录类型
 * ===========================
 */

export interface BaseChatRecord<T extends ChatRecordType> {
    id: string
    type: T
    content: ContentTypeMap[T]
    timestamp: number // 聊天记录时间戳；单位是“毫秒”（和企业微信内部生成的 send_time 等不同，那个是秒级的）
    serverId: string // 消息在腾讯服务器端中的 id。发送引用消息时会用到
}

export interface SendChatRecord<T extends ChatRecordType> extends BaseChatRecord<T> {
    isSend: true
    receiptId?: string // 消息发送成功后 MQTT 推送的回执（（同时也是最终记录下来的聊天记录内容））会包含此字段，对应之前临时生成的“发送凭证 ID”
    byRobot: null | RobotInfo // 由机器人发送时，此字段为机器人意图信息；否则为 null
    byOpenAPI: boolean // 是否通过开放平台接口发送
    byChatFlow: null | string // 由“私聊工作流”发送时，此为工作流名称
    accountId?: number // 实际发送者的登录账号 ID。仅当消息发送账号不是当前账号时有值（如涉及转交、观察者模式，此时消息发送者不一定是原所有者）
    accountName?: string // 实际发送者的登录账号用户名
    success: null | boolean // 是否发送成功；null 确认中, true 成功, false 失败。对于有 receiptId 的情况，此字段标记了回执对应的原消息发送成功与否。
    isRecall?: boolean // 是否已撤回
}

export interface RobotInfo {
    hit_node: string // 命中意图
    hit_title: string // 命中技能
}

export interface RecvChatRecord<T extends ChatRecordType> extends BaseChatRecord<T> {
    isSend: false
    matchWarnRules: string[] // 匹配到的预警规则列表
    groupSenderWxid: string // 群消息中，发送消息的群成员的 wxid；其他情况此项为空
    groupSenderName: string // 同上，发消息的群成员昵称
}

/**
 * ===========================
 * 枚举所有类型的聊天记录
 * ===========================
 */

export enum ChatRecordType {
    ParseFailed = 'ParseFailed', // 解析失败的聊天记录
    Text = 'Text',
    Image = 'Image',
    Video = 'Video',
    Audio = 'Audio',
    File = 'File',
    Emotion = 'Emotion', // 表情（gif 或静态图）
    LinkCard = 'LinkCard', // 链接卡片
    PersonCard = 'PersonCard', // 名片
    MiniProgram = 'MiniProgram', // 小程序
    Videonum = 'Videonum', // 视频号
    Position = 'Position', // 位置
    RedPocket = 'RedPocket', // 红包
    TransferMoney = 'TransferMoney', // 转账
    GroupAdd = 'GroupAdd', // 群拉人
    GroupRemove = 'GroupRemove', // 群踢人
    ContactNotify = 'ContactNotify', // 好友状态通知。如：因删除联系人而发送消息失败、已添加好友可以开始聊天
    Call = 'Call', // 语音/视频通话
}

export type ContentTypeMap = {
    [ChatRecordType.ParseFailed]: ParseFailedContent
    [ChatRecordType.Text]: TextContent
    [ChatRecordType.Image]: ImageContent
    [ChatRecordType.Video]: VideoContent
    [ChatRecordType.Audio]: AudioContent
    [ChatRecordType.File]: FileContent
    [ChatRecordType.Emotion]: EmotionContent
    [ChatRecordType.LinkCard]: LinkCardContent
    [ChatRecordType.PersonCard]: PersonCardContent
    [ChatRecordType.MiniProgram]: MiniProgramContent
    [ChatRecordType.Videonum]: VideonumContent
    [ChatRecordType.Position]: PositionContent
    [ChatRecordType.RedPocket]: RedPocketContent
    [ChatRecordType.TransferMoney]: TransferMoneyContent
    [ChatRecordType.GroupAdd]: GroupMemberUpdateContent
    [ChatRecordType.GroupRemove]: GroupMemberUpdateContent
    [ChatRecordType.ContactNotify]: ContactNotifyContent
    [ChatRecordType.Call]: CallContent
}

export const contentFormatters = {
    [ChatRecordType.Text]: formatTextContent,
    [ChatRecordType.Image]: formatRawImageContent,
    [ChatRecordType.Video]: formatRawVideoContent,
    [ChatRecordType.Audio]: formatRawAudioContent,
    [ChatRecordType.File]: formatRawFilecontent,
    [ChatRecordType.Emotion]: extractEmotionContent,
    [ChatRecordType.LinkCard]: formatRawLinkCardContent,
    [ChatRecordType.PersonCard]: formatRawPersonCard,
    [ChatRecordType.MiniProgram]: extractMiniProgramContent,
    [ChatRecordType.Videonum]: formatRawVideonumContent,
    [ChatRecordType.Position]: formatRawPositionContent,
    [ChatRecordType.RedPocket]: formatRawRedPocketContent,
    [ChatRecordType.TransferMoney]: formatRawTransferMoneyContent,
    [ChatRecordType.GroupAdd]: formatRawGroupMemberUpdateContent,
    [ChatRecordType.GroupRemove]: formatRawGroupMemberUpdateContent,
    [ChatRecordType.ContactNotify]: extractContactNotifyContent,
    [ChatRecordType.Call]: extractCallContent,
}

type SendR<T extends ChatRecordType> = SendChatRecord<T>
type RecvR<T extends ChatRecordType> = RecvChatRecord<T>
type SendAndRecvR<T extends ChatRecordType> = SendR<T> | RecvR<T>
export type { SendAndRecvR as SendAndRecvChatRecord }

export type ParseFailedRecord = SendAndRecvR<ChatRecordType.ParseFailed>
export type TextRecord = SendAndRecvR<ChatRecordType.Text>
export type ImageRecord = SendAndRecvR<ChatRecordType.Image>
export type VideoRecord = SendAndRecvR<ChatRecordType.Video>
export type AudioRecord = SendAndRecvR<ChatRecordType.Audio>
export type FileRecord = SendAndRecvR<ChatRecordType.File>
export type EmotionRecord = SendAndRecvR<ChatRecordType.Emotion>
export type LinkCardRecord = SendAndRecvR<ChatRecordType.LinkCard>
export type PersonCardRecord = SendAndRecvR<ChatRecordType.PersonCard>
export type MiniProgramRecord = SendAndRecvR<ChatRecordType.MiniProgram>
export type VideonumRecord = SendAndRecvR<ChatRecordType.Videonum>
export type PositionRecord = SendAndRecvR<ChatRecordType.Position>
export type RedPocketRecord = SendAndRecvR<ChatRecordType.RedPocket>
export type TransferMoneyRecord = SendAndRecvR<ChatRecordType.TransferMoney>
export type GroupAddRecord = RecvR<ChatRecordType.GroupAdd>
export type GroupRemoveRecord = RecvR<ChatRecordType.GroupRemove>
export type ContactNotifyRecord = RecvR<ChatRecordType.ContactNotify>
export type CallRecord = RecvR<ChatRecordType.Call>

export type ChatRecordMap = {
    [ChatRecordType.ParseFailed]: ParseFailedRecord
    [ChatRecordType.Text]: TextRecord
    [ChatRecordType.Image]: ImageRecord
    [ChatRecordType.Video]: VideoRecord
    [ChatRecordType.Audio]: AudioRecord
    [ChatRecordType.File]: FileRecord
    [ChatRecordType.Emotion]: EmotionRecord
    [ChatRecordType.LinkCard]: LinkCardRecord
    [ChatRecordType.PersonCard]: PersonCardRecord
    [ChatRecordType.MiniProgram]: MiniProgramRecord
    [ChatRecordType.Videonum]: VideonumRecord
    [ChatRecordType.Position]: PositionRecord
    [ChatRecordType.RedPocket]: RedPocketRecord
    [ChatRecordType.TransferMoney]: TransferMoneyRecord
    [ChatRecordType.GroupAdd]: GroupAddRecord
    [ChatRecordType.GroupRemove]: GroupRemoveRecord
    [ChatRecordType.ContactNotify]: ContactNotifyRecord
    [ChatRecordType.Call]: CallRecord
}

export type ChatRecord = ChatRecordMap[ChatRecordType]

/**
 * ===========================
 * 定义各类型聊天记录的内容格式
 * ===========================
 */

export interface ParseFailedContent {
    message: string // 解析失败的原因
    type?: number // MQTT 消息类型
    appinfo?: string // 消息内容中可能存在的 appinfo（可基于此分析这条消息是不是一种还不支持的消息类型）
}

export interface RawTextContent {
    at_list: { user_id: string; nickname: string }[] // 群聊中被 @ 的人的列表，目前没用上，因为消息内容里已包含了这些人的名字
    content: string,
    quote_content: string
}
export interface TextContent {
    atList: { userId: string; nickname: string }[]
    text: string
}
export function formatTextContent(raw: RawTextContent) {
    return {
        text: raw.quote_content != null && raw.quote_content!='' ? raw.quote_content : raw.content,
        atList: raw.at_list.map(v => ({ userId: v.user_id, nickname: v.nickname })),
    }
}

export interface RawImageContent {
    file_path: string
    qrcodeUrl?: string
    imgWidth?: number
    imgHeight?: number
}
export interface ImageContent {
    url: string // 图片文件 URL（相对路径）
    qrcode?: string // 如果识别到图片里有二维码，此为识别出的二维码内容
    width?: number // 图片宽度
    height?: number // 图片高度
}
export function formatRawImageContent(raw: RawImageContent): ImageContent {
    return {
        url: raw.file_path,
        qrcode: raw.qrcodeUrl ?? undefined,
        width: raw.imgWidth ?? undefined,
        height: raw.imgHeight ?? undefined,
    }
}

export interface RawVideoContent {
    file_path: string
    file_size: number // 视频文件大小(Byte)
    width: number // 经过换算的视频宽度
    height: number // 经过换算的视频高度
    duration: number // 视频时长
    videoWidth?: number
    videoHeight?: number
}
export interface VideoContent {
    url: string // 视频文件 URL（相对路径）
    width?: number // 视频原始宽度
    height?: number // 视频原始高度
}
export function formatRawVideoContent(raw: RawVideoContent): VideoContent {
    return {
        url: raw.file_path,
        width: raw.videoWidth,
        height: raw.videoHeight,
    }
}

export interface RawAudioContent {
    file_path: string
    file_size: number
    duration: string // 音频时长
    text?: string
}
export interface AudioContent {
    url: string // 音频文件 URL
    text?: string // 音频转文本解析结果
}
export function formatRawAudioContent(raw: RawAudioContent): AudioContent {
    return {
        url: raw.file_path,
        text: raw.text,
    }
}

export interface RawAudioSpeechResult {
    ConversationId: string
    MsgId: string
    SpeechResult: string
    MatchWarnRules?: string[]
}
export interface AudioSpeechResult {
    conversationId: string // 此识别结果来自哪个联系人的消息
    msgId: string // 此识别结果来自哪条消息
    text: string // 语音识别结果
    matchWarnRules: string[] // 文字内容匹配到的预警规则列表
}
export function formatRawAudioSpeechResult(raw: RawAudioSpeechResult): AudioSpeechResult {
    return {
        conversationId: raw.ConversationId,
        msgId: raw.MsgId,
        text: raw.SpeechResult,
        matchWarnRules: raw.MatchWarnRules ?? [],
    }
}

export interface RawFileContent {
    file_path: string
    file_size?: number
}
export interface FileContent {
    url: string // 文件 URL
    name: string // 文件名（目前是从 URL 中解析出来的）
    size: number // 文件大小(Byte)
}
export function formatRawFilecontent(raw: RawFileContent): FileContent {
    return {
        url: raw.file_path,
        name: getFilenameFromPath(raw.file_path),
        size: raw.file_size ?? 0,
    }
}

export interface EmotionContent {
    name: '动画表情'
    url: string
}
export function extractEmotionContent(raw: EmotionContent): EmotionContent {
    return pick(raw, 'name', 'url')
}

export interface RawLinkCardContent {
    desc: string
    image_url: string
    title: string
    url: string
}
export interface LinkCardContent {
    title: string // 标题
    desc: string // 描述文字
    image: string // 图片网址
    url: string // 链接网址
}
export function formatRawLinkCardContent(raw: RawLinkCardContent): LinkCardContent {
    return {
        title: raw.title,
        desc: raw.desc,
        image: raw.image_url,
        url: raw.url,
    }
}

export interface RawPersonCardContent {
    avatar: string
    nickname: string
    user_id: string
}
export interface PersonCardContent {
    avatar: string
    nickname: string
    userId: string
}
export function formatRawPersonCard(raw: RawPersonCardContent): PersonCardContent {
    return {
        avatar: makeHTTPSAvatar(raw.avatar),
        nickname: raw.nickname,
        userId: raw.user_id,
    }
}

// 小程序内容字段太多，但大多只有传递意义，没有使用上的作用，因此不做格式化
export interface MiniProgramContent {
    username: string // gh_3a18fa53cb80@app
    appid: string // wx723bb5bb25362f6c
    appname: string // 小程序名称
    appicon: string // 小程序图标
    title: string // 小程序分享标题
    page_path: string // 小程序分享页面路径
    cover_path: string // 小程序封面图
    // 数据里有，但是后端不需要的字段
    // appinfo: string, // from_msgid_6197600668870351583
    // thumb_height: number,
    // thumb_width: number,
}
export function extractMiniProgramContent(raw: MiniProgramContent): MiniProgramContent {
    return pick(raw, 'username', 'appid', 'appname', 'appicon', 'title', 'page_path', 'cover_path')
}

export type VideonumContent = Omit<VideonumWord, 'type'>
export function formatRawVideonumContent(raw: VideonumContent): VideonumContent {
    return raw
}

export interface PositionContent {
    title: string // 位置标题
    address: string // 地址
    latitude: number // 纬度
    longitude: number // 经度
    zoom: number // 缩放等级
    appinfo: string // 用途不明
    content_type: 6 // 用途不明
}
export function formatRawPositionContent(raw: PositionContent): PositionContent {
    return raw
}

export interface RedPocketContent {
    remark: string // 红包备注（发红包时填写）
    desc: string // 红包描述（如 '来自XXX的红包，请进入手机版企业微信查看'）
    money: number // 红包金额（单位：分）
    packet_id: string // 红包 ID
    appinfo: string // 用途不明
    content_type: 26 // 用途不明
}
export function formatRawRedPocketContent(raw: RedPocketContent): RedPocketContent {
    return raw
}

export interface TransferMoneyContent {
    appinfo: string
    isReceived: boolean // 为 false 是客户发起转账；为 true 是接收了转账
}
export function formatRawTransferMoneyContent(raw: { appinfo: string }): TransferMoneyContent {
    return {
        appinfo: raw.appinfo,
        isReceived: raw.appinfo.endsWith('_receiver'),
    }
}

export interface RawGroupMemberUpdateContent {
    op_user_id: string
    op_user_name: string
    room_conversation_id: string
    room_name: string
    member_list: RawSimpleGroupMember[]
}
export interface GroupMemberUpdateContent {
    conversationId: string // 拉人/踢人的群的 conversationId
    name: string // 拉人/踢人的群的名称
    opUserId: string // 进行拉人/踢人操作的人的 userId
    opUserName: string // 进行拉人/踢人操作的人的名称
    members: SimpleGroupMember[] // 拉/踢人的人员列表
}
export interface RawSimpleGroupMember {
    user_id: string
    name: string
}
export interface SimpleGroupMember {
    userId: string
    name: string
}
export function formatRawGroupMemberUpdateContent(
    raw: RawGroupMemberUpdateContent,
): GroupMemberUpdateContent {
    return {
        conversationId: raw.room_conversation_id,
        name: raw.room_name,
        opUserId: raw.op_user_id,
        opUserName: raw.op_user_name,
        members: raw.member_list.map(item => ({ name: item.name, userId: item.user_id })),
    }
}

export interface ContactNotifyContent {
    content: string // content 可能为空，代表是因已删除好友而发送失败
}
export function extractContactNotifyContent(raw: ContactNotifyContent): ContactNotifyContent {
    return {
        content:
            raw.content ||
            '对方开启了联系人验证，你还不是他（她）的联系人。请先发送联系人验证请求，对方验证通过后，才能聊天',
    }
}

export interface RawCallContent {
    inviteMsg: {
        inviteId: string | 0 // 0 是己方发起的，string 是对方发起的
    }
}

export type CallContent = Record<string, never>
export function extractCallContent(raw: CallContent): CallContent {
    return raw
}

/**
 * ===========================
 * 相关函数
 * ===========================
 */

/**
 * 生成一条发送聊天消息的聊天记录
 */
export function makeSendChatRecord<T extends ChatRecordType>(
    type: T,
    content: ContentTypeMap[T],
    id?: string,
): SendChatRecord<T> {
    return {
        type,
        isSend: true,
        id: id ?? Math.round(Math.random() * 1000000).toString(),
        serverId: '',
        content,
        timestamp: new Date().getTime(),
        byRobot: null,
        byOpenAPI: false,
        byChatFlow: null,
        success: null,
    }
}

/**
 * 返回各类型聊天记录的纯文本格式（例如用于引用消息）
 */
export function chatRecordToText(record: ChatRecord) {
    switch (record.type) {
        case ChatRecordType.ParseFailed:
            return record.content.type !== undefined
                ? `[不支持的消息] ${(record.content.appinfo ?? '') || record.content.type}`
                : '[解析失败]'
        case ChatRecordType.Text:
            return record.content.text
        case ChatRecordType.Image:
            return '[图片]'
        case ChatRecordType.Video:
            return '[视频]'
        case ChatRecordType.Audio:
            return '[语音]'
        case ChatRecordType.File:
            return `[文件]${record.content.name}`
        case ChatRecordType.Emotion:
            return '[表情]'
        case ChatRecordType.LinkCard:
            return `[链接]${record.content.title}`
        case ChatRecordType.PersonCard:
            return `[名片]${record.content.nickname}`
        case ChatRecordType.MiniProgram:
            return `[小程序]${record.content.title}`
        case ChatRecordType.Videonum:
            return `[视频号]${record.content.desc}`
        case ChatRecordType.Position:
            return `[位置]${record.content.title}`
        case ChatRecordType.RedPocket:
            return `[红包]${record.content.remark}`
        case ChatRecordType.TransferMoney:
            return `[转账]`
        case ChatRecordType.GroupAdd:
            return '[群拉人]'
        case ChatRecordType.GroupRemove:
            return '[群踢人]'
        case ChatRecordType.ContactNotify:
            return record.content.content
        case ChatRecordType.Call:
            return '[语音视频]'
    }

    // @ts-ignore 保留此分支作为保险
    logger.error(`不支持文本化的消息：${record.type}`, record)
    return ''
}
